package ui;

import java.awt.AWTException;
import java.awt.Color;
import java.awt.Font;
import java.awt.Frame;
import java.awt.GradientPaint;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Point;
import java.awt.RenderingHints;
import java.awt.Robot;
import java.awt.Shape;
import java.awt.Toolkit;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.Transferable;
import java.awt.datatransfer.UnsupportedFlavorException;
import java.awt.dnd.DnDConstants;
import java.awt.dnd.DropTarget;
import java.awt.dnd.DropTargetAdapter;
import java.awt.dnd.DropTargetDropEvent;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.awt.event.MouseWheelEvent;
import java.awt.event.MouseWheelListener;
import java.awt.event.WindowEvent;
import java.awt.font.TextLayout;
import java.awt.geom.AffineTransform;
import java.awt.geom.Area;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Path2D;
import java.awt.geom.PathIterator;
import java.awt.geom.Point2D;
import java.awt.geom.RoundRectangle2D;
import java.awt.image.BufferedImage;
import java.awt.image.ConvolveOp;
import java.awt.image.Kernel;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.List;
import java.util.Random;
import java.util.TimeZone;
import java.util.Vector;

import javax.imageio.ImageIO;
import javax.swing.JPanel;
import javax.swing.text.DateFormatter;

import tetris.Tetris;

public class FramePane extends JPanel implements MouseListener, MouseMotionListener, MouseWheelListener, KeyListener {
	private static final URL URL_IMG_MIN = FramePane.class.getResource("/res/images/min.png");
	private static final URL URL_IMG_CLOSE = FramePane.class.getResource("/res/images/close.png");

	private Robot robot;
	private Random rnd = new Random();
	private BufferedImage backgroundOrigin;
	private BufferedImage backgroundFullScreen;
	private BufferedImage[][] backgroundSmall, backgroundSmallTranscluent;
	private Path2D homeScreenPath = new Path2D.Float();
	private BufferedImage textShadows;
	private BufferedImage fakeFrame;
	private BufferedImage border;
	private BufferedImage drops;
	private BufferedImage gamePanelContent;
	private BufferedImage homeScreenSurface;
	private BufferedImage backgroundLetterT;
	private BufferedImage[] shapeTypes = new BufferedImage[5];
	private BufferedImage[] coloredShapeTypes = new BufferedImage[5];
	private BufferedImage btnCloseIcon, btnMinIcon, btnStartImg, btnStartOverImg, btnStartIcon, btnStartIconOver, btnStartDownImg;
	private MovieClip backgroundShapeDroppingMovieClip;
	private Vector<MovieClip> mouseClickedBlockRoundingMovieClips = new Vector<MovieClip>();
	private Vector<MovieClip> eliminatingMovieClips = new Vector<MovieClip>();
	private SpreadingCover spreadingCover;
	private int unitSize = 20;
	private int rows, cols;
	private FullScreenFrame parent;
	private int gamePanelX = 20, gamePanelY = 60, gamePanelWidth, gamePanelHeight;
	private int gamePanelPaddingX = 5, gamePanelPaddingY = 5;
	private float x, y, width, height;
	private int mouseX, mouseY;
	private boolean[][] backgroundDots;

	private int over = -1, clicked = -1, down = -1;
	private CustomComponent btnMin = new CustomComponent(new RoundRectangle2D.Float(0, 0, 30, 24, 0, 0), "", false, true);
	private CustomComponent btnClose = new CustomComponent(new RoundRectangle2D.Float(0, 0, 50, 24, 0, 0), "", false, true);
	private CustomComponent btnStart = new CustomComponent(new RoundRectangle2D.Float(0, 0, 100, 100, 0, 0), "", false, true);
	private CustomComponent btnClassic = new CustomComponent(new RoundRectangle2D.Float(0, 0, 240, 240, 0, 0), "", false, true);
	private CustomComponent btnChallenge = new CustomComponent(new RoundRectangle2D.Float(0, 0, 280, 280, 0, 0), "", false, true);
	private CustomComponent lblTimer = new CustomComponent(new RoundRectangle2D.Float(0, 0, 120, 30, 8, 8), "00:00:00", false, true);
	private CustomComponent lblScore = new CustomComponent(new RoundRectangle2D.Float(0, 0, 120, 30, 8, 8), "0/0", false, true);
	private CustomComponent lblElim = new CustomComponent(new RoundRectangle2D.Float(0, 0, 120, 30, 8, 8), "0/0", false, true);
	private CustomComponent lblPreview = new CustomComponent(new RoundRectangle2D.Float(0, 0, 120, 120, 8, 8), "", false, true);
	private CustomComponent mnuHome = new CustomComponent(new RoundRectangle2D.Float(0, 0, 120, 30, 8, 8), "Home", true, false);
	private CustomComponent mnuStart = new CustomComponent(new RoundRectangle2D.Float(0, 0, 120, 30, 8, 8), "Start", true, false);
	private CustomComponent mnuStop = new CustomComponent(new RoundRectangle2D.Float(0, 0, 120, 30, 8, 8), "Stop", true, false);

	private final CustomComponent[] compMap = {btnMin, btnClose, btnStart, btnClassic, btnChallenge, mnuHome, mnuStart, mnuStop};

	private Tetris tetris = new Tetris(this);
	private Thread thdTetris;
	private int mode = -1;
	private static final int MODE_CLASSIC = 0;
	private static final int MODE_CHALLENGE = 1;

	private long updateRate = 20;
	private Thread thdRepaintMain = new Thread() {
		@Override
		public void run () {
			int loop = 0;
			while (true) {
				try {
					if (loop % 20 == 19) {
						synchronized (mouseClickedBlockRoundingMovieClips) {
							for (int i = 0; i < mouseClickedBlockRoundingMovieClips.size(); i++) {
								if (mouseClickedBlockRoundingMovieClips.get(i).isStopped()) {
									mouseClickedBlockRoundingMovieClips.remove(i);
								} else {
									i++;
								}
							}
						}
						synchronized (eliminatingMovieClips) {
							for (int i = 0; i < eliminatingMovieClips.size(); i++) {
								if (eliminatingMovieClips.get(i).isStopped()) {
									eliminatingMovieClips.remove(i);
								} else {
									i++;
								}
							}
						}
					}

					repaint();
					Thread.sleep(updateRate);
					loop = (loop + 1) % 100;
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
		}
	};

	private Thread thdRepaintHomeScreenSurface;

	private Thread thdTimer = new Thread() {
		DateFormat df = new SimpleDateFormat("HH:mm:ss");

		@Override
		public void run () {
			df.setTimeZone(TimeZone.getTimeZone("GMT"));
			DateFormatter formatter = new DateFormatter(df);
			while (true) {
				try {
					if (lblTimer.isVisible()) {
						lblTimer.setLabel(formatter.valueToString(tetris.getTime()));
					}
				} catch (ParseException e) {
					e.printStackTrace();
				}
				try {
					Thread.sleep(500);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
		}
	};

	public FramePane (FullScreenFrame parent, BufferedImage bg) {
		this.parent = parent;
		backgroundOrigin = bg;

		try {
			robot = new Robot();
		} catch (AWTException e2) {
			e2.printStackTrace();
		}

		initControls();
		initBlockTypes();
		SplashWindow spl = new SplashWindow();
		thdTimer.start();
		initAll();
		setBackground(new Color(0, 0, 0, 0));
		setDoubleBuffered(true);

		addMouseListener(this);
		addMouseMotionListener(this);
		addMouseWheelListener(this);
		addKeyListener(this);

		new DropTarget(this, new DropTargetAdapter() {

			@Override
			public void drop (DropTargetDropEvent dtde) {
				dtde.acceptDrop(DnDConstants.ACTION_COPY_OR_MOVE);
				Transferable trans = dtde.getTransferable();
				try {
					@SuppressWarnings ("unchecked") final List<File> files = (List<File>) trans.getTransferData(DataFlavor.javaFileListFlavor);
					new Thread() {
						@Override
						public void run () {
							try {
								backgroundOrigin = ImageIO.read(files.get(0));
								stopAllMovie();
								initAll();
								repaint();
							} catch (IOException e) {
								e.printStackTrace();
							}
						}
					}.start();
				} catch (UnsupportedFlavorException e) {
					e.printStackTrace();
				} catch (IOException e1) {
					e1.printStackTrace();
				}
			}

		});

		thdRepaintMain.start();
		spl.close();
	}

	private void initBlockTypes () {
		initBlockType0();
		initBlockType1();
		initBlockType2();
		initBlockType3();
		initBlockType4();
	}

	/*
	 * [][][][]
	 */
	private void initBlockType0 () {
		shapeTypes[0] = new BufferedImage(132, 66, BufferedImage.TYPE_INT_ARGB);
		Graphics2D g0 = shapeTypes[0].createGraphics();
		g0.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
		g0.setRenderingHint(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_QUALITY);
		g0.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
		g0.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
		g0.setColor(new Color(250, 250, 250, 90));
		g0.fillRoundRect(22, 22, 22, 26, 8, 8);
		g0.fillRoundRect(44, 22, 22, 26, 8, 8);
		g0.fillRoundRect(66, 22, 22, 26, 8, 8);
		g0.fillRoundRect(88, 22, 22, 26, 8, 8);
		shapeTypes[0].flush();
		g0.dispose();
		shapeTypes[0] = blur(shapeTypes[0], 10);

		g0 = shapeTypes[0].createGraphics();
		g0.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
		g0.setRenderingHint(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_QUALITY);
		g0.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
		g0.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
		g0.setColor(new Color(250, 250, 250, 60));
		g0.fillRoundRect(29, 27, 16, 16, 5, 5);
		g0.fillRoundRect(48, 27, 16, 16, 5, 5);
		g0.fillRoundRect(67, 27, 16, 16, 5, 5);
		g0.fillRoundRect(86, 27, 16, 16, 5, 5);
		g0.setColor(new Color(50, 50, 55, 220));
		g0.drawRoundRect(29, 27, 16, 16, 5, 5);
		g0.drawRoundRect(48, 27, 16, 16, 5, 5);
		g0.drawRoundRect(67, 27, 16, 16, 5, 5);
		g0.drawRoundRect(86, 27, 16, 16, 5, 5);
		shapeTypes[0].flush();
		g0.dispose();
		shapeTypes[0] = blur(shapeTypes[0], 5);

		coloredShapeTypes[0] = new BufferedImage(132, 66, BufferedImage.TYPE_INT_ARGB);
		Graphics2D g1 = coloredShapeTypes[0].createGraphics();
		g1.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
		g1.setRenderingHint(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_QUALITY);
		g1.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
		g1.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
		g1.setColor(new Color(180, 240, 250, 60));
		g1.fillRoundRect(22, 22, 22, 26, 8, 8);
		g1.fillRoundRect(44, 22, 22, 26, 8, 8);
		g1.fillRoundRect(66, 22, 22, 26, 8, 8);
		g1.fillRoundRect(88, 22, 22, 26, 8, 8);
		g1.setColor(new Color(250, 50, 50, 50));
		g1.drawRoundRect(29, 27, 16, 16, 5, 5);
		g1.drawRoundRect(48, 27, 16, 16, 5, 5);
		g1.drawRoundRect(67, 27, 16, 16, 5, 5);
		g1.drawRoundRect(86, 27, 16, 16, 5, 5);
		coloredShapeTypes[0].flush();
		g1.dispose();
		coloredShapeTypes[0] = blur(coloredShapeTypes[0], 5);
	}

	/**
	 * [][][]
	 *   []
	 */
	private void initBlockType1 () {
		shapeTypes[1] = new BufferedImage(110, 88, BufferedImage.TYPE_INT_ARGB);
		Graphics2D g = shapeTypes[1].createGraphics();
		g.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
		g.setRenderingHint(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_QUALITY);
		g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
		g.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
		g.setColor(new Color(250, 250, 250, 90));
		g.fillRoundRect(22, 22, 22, 26, 8, 8);
		g.fillRoundRect(44, 22, 22, 26, 8, 8);
		g.fillRoundRect(66, 22, 22, 26, 8, 8);
		g.fillRoundRect(44, 40, 22, 26, 8, 8);
		shapeTypes[1].flush();
		g.dispose();
		shapeTypes[1] = blur(shapeTypes[1], 10);

		g = shapeTypes[1].createGraphics();
		g.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
		g.setRenderingHint(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_QUALITY);
		g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
		g.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
		g.setColor(new Color(250, 250, 250, 60));
		g.fillRoundRect(29, 27, 16, 16, 5, 5);
		g.fillRoundRect(48, 27, 16, 16, 5, 5);
		g.fillRoundRect(67, 27, 16, 16, 5, 5);
		g.fillRoundRect(48, 46, 16, 16, 5, 5);
		g.setColor(new Color(50, 50, 55, 220));
		g.drawRoundRect(29, 27, 16, 16, 5, 5);
		g.drawRoundRect(48, 27, 16, 16, 5, 5);
		g.drawRoundRect(67, 27, 16, 16, 5, 5);
		g.drawRoundRect(48, 46, 16, 16, 5, 5);
		shapeTypes[1].flush();
		g.dispose();
		shapeTypes[1] = blur(shapeTypes[1], 5);

		coloredShapeTypes[1] = new BufferedImage(110, 88, BufferedImage.TYPE_INT_ARGB);
		Graphics2D g1 = coloredShapeTypes[1].createGraphics();
		g1.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
		g1.setRenderingHint(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_QUALITY);
		g1.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
		g1.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
		g1.setColor(new Color(180, 240, 250, 60));
		g1.fillRoundRect(22, 22, 22, 26, 8, 8);
		g1.fillRoundRect(44, 22, 22, 26, 8, 8);
		g1.fillRoundRect(66, 22, 22, 26, 8, 8);
		g1.fillRoundRect(44, 40, 22, 26, 8, 8);
		g1.setColor(new Color(250, 50, 50, 50));
		g1.drawRoundRect(29, 27, 16, 16, 5, 5);
		g1.drawRoundRect(48, 27, 16, 16, 5, 5);
		g1.drawRoundRect(67, 27, 16, 16, 5, 5);
		g1.drawRoundRect(48, 46, 16, 16, 5, 5);
		coloredShapeTypes[1].flush();
		g1.dispose();
		coloredShapeTypes[1] = blur(coloredShapeTypes[1], 5);
	}

	/**
	 * [][][]
	 * []
	 */
	private void initBlockType2 () {
		shapeTypes[2] = new BufferedImage(110, 88, BufferedImage.TYPE_INT_ARGB);
		Graphics2D g = shapeTypes[2].createGraphics();
		g.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
		g.setRenderingHint(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_QUALITY);
		g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
		g.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
		g.setColor(new Color(250, 250, 250, 90));
		g.fillRoundRect(22, 22, 22, 26, 8, 8);
		g.fillRoundRect(44, 22, 22, 26, 8, 8);
		g.fillRoundRect(66, 22, 22, 26, 8, 8);
		g.fillRoundRect(22, 40, 27, 26, 8, 8);
		shapeTypes[2].flush();
		g.dispose();
		shapeTypes[2] = blur(shapeTypes[2], 10);

		g = shapeTypes[2].createGraphics();
		g.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
		g.setRenderingHint(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_QUALITY);
		g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
		g.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
		g.setColor(new Color(250, 250, 250, 60));
		g.fillRoundRect(29, 27, 16, 16, 5, 5);
		g.fillRoundRect(48, 27, 16, 16, 5, 5);
		g.fillRoundRect(67, 27, 16, 16, 5, 5);
		g.fillRoundRect(29, 46, 16, 16, 5, 5);
		g.setColor(new Color(50, 50, 55, 220));
		g.drawRoundRect(29, 27, 16, 16, 5, 5);
		g.drawRoundRect(48, 27, 16, 16, 5, 5);
		g.drawRoundRect(67, 27, 16, 16, 5, 5);
		g.drawRoundRect(29, 46, 16, 16, 5, 5);
		shapeTypes[2].flush();
		g.dispose();
		shapeTypes[2] = blur(shapeTypes[2], 5);

		coloredShapeTypes[2] = new BufferedImage(110, 88, BufferedImage.TYPE_INT_ARGB);
		Graphics2D g1 = coloredShapeTypes[2].createGraphics();
		g1.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
		g1.setRenderingHint(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_QUALITY);
		g1.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
		g1.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
		g1.setColor(new Color(180, 240, 250, 60));
		g1.fillRoundRect(22, 22, 22, 26, 8, 8);
		g1.fillRoundRect(44, 22, 22, 26, 8, 8);
		g1.fillRoundRect(66, 22, 22, 26, 8, 8);
		g1.fillRoundRect(22, 40, 27, 26, 8, 8);
		g1.setColor(new Color(250, 50, 50, 50));
		g1.drawRoundRect(29, 27, 16, 16, 5, 5);
		g1.drawRoundRect(48, 27, 16, 16, 5, 5);
		g1.drawRoundRect(67, 27, 16, 16, 5, 5);
		g1.drawRoundRect(29, 46, 16, 16, 5, 5);
		coloredShapeTypes[2].flush();
		g1.dispose();
		coloredShapeTypes[2] = blur(coloredShapeTypes[2], 5);
	}

	/**
	 * [][]
	 * [][]
	 */
	private void initBlockType3 () {
		shapeTypes[3] = new BufferedImage(88, 88, BufferedImage.TYPE_INT_ARGB);
		Graphics2D g = shapeTypes[3].createGraphics();
		g.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
		g.setRenderingHint(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_QUALITY);
		g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
		g.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
		g.setColor(new Color(250, 250, 250, 90));
		g.fillRoundRect(22, 22, 27, 26, 8, 8);
		g.fillRoundRect(44, 22, 27, 26, 8, 8);
		g.fillRoundRect(22, 40, 27, 26, 8, 8);
		g.fillRoundRect(44, 40, 27, 26, 8, 8);
		shapeTypes[3].flush();
		g.dispose();
		shapeTypes[3] = blur(shapeTypes[3], 10);

		g = shapeTypes[3].createGraphics();
		g.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
		g.setRenderingHint(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_QUALITY);
		g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
		g.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
		g.setColor(new Color(250, 250, 250, 60));
		g.fillRoundRect(29, 27, 16, 16, 5, 5);
		g.fillRoundRect(48, 27, 16, 16, 5, 5);
		g.fillRoundRect(29, 46, 16, 16, 5, 5);
		g.fillRoundRect(48, 46, 16, 16, 5, 5);
		g.setColor(new Color(50, 50, 55, 220));
		g.drawRoundRect(29, 27, 16, 16, 5, 5);
		g.drawRoundRect(48, 27, 16, 16, 5, 5);
		g.drawRoundRect(29, 46, 16, 16, 5, 5);
		g.drawRoundRect(48, 46, 16, 16, 5, 5);
		shapeTypes[3].flush();
		g.dispose();
		shapeTypes[3] = blur(shapeTypes[3], 5);

		coloredShapeTypes[3] = new BufferedImage(88, 88, BufferedImage.TYPE_INT_ARGB);
		Graphics2D g1 = coloredShapeTypes[3].createGraphics();
		g1.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
		g1.setRenderingHint(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_QUALITY);
		g1.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
		g1.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
		g1.setColor(new Color(180, 240, 250, 60));
		g1.fillRoundRect(22, 22, 27, 26, 8, 8);
		g1.fillRoundRect(44, 22, 27, 26, 8, 8);
		g1.fillRoundRect(22, 40, 27, 26, 8, 8);
		g1.fillRoundRect(44, 40, 27, 26, 8, 8);
		g1.setColor(new Color(250, 50, 50, 50));
		g1.drawRoundRect(29, 27, 16, 16, 5, 5);
		g1.drawRoundRect(48, 27, 16, 16, 5, 5);
		g1.drawRoundRect(29, 46, 16, 16, 5, 5);
		g1.drawRoundRect(48, 46, 16, 16, 5, 5);
		coloredShapeTypes[3].flush();
		g1.dispose();
		coloredShapeTypes[3] = blur(coloredShapeTypes[3], 5);
	}

	/**
	 *   [][]
	 * [][]
	 */
	private void initBlockType4 () {
		shapeTypes[4] = new BufferedImage(110, 88, BufferedImage.TYPE_INT_ARGB);
		Graphics2D g = shapeTypes[4].createGraphics();
		g.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
		g.setRenderingHint(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_QUALITY);
		g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
		g.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
		g.setColor(new Color(250, 250, 250, 90));
		g.fillRoundRect(44, 22, 22, 26, 8, 8);
		g.fillRoundRect(66, 22, 22, 26, 8, 8);
		g.fillRoundRect(22, 40, 27, 26, 8, 8);
		g.fillRoundRect(44, 40, 27, 26, 8, 8);
		shapeTypes[4].flush();
		g.dispose();
		shapeTypes[4] = blur(shapeTypes[4], 10);

		g = shapeTypes[4].createGraphics();
		g.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
		g.setRenderingHint(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_QUALITY);
		g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
		g.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
		g.setColor(new Color(250, 250, 250, 60));
		g.fillRoundRect(48, 27, 16, 16, 5, 5);
		g.fillRoundRect(67, 27, 16, 16, 5, 5);
		g.fillRoundRect(29, 46, 16, 16, 5, 5);
		g.fillRoundRect(48, 46, 16, 16, 5, 5);
		g.setColor(new Color(50, 50, 55, 220));
		g.drawRoundRect(48, 27, 16, 16, 5, 5);
		g.drawRoundRect(67, 27, 16, 16, 5, 5);
		g.drawRoundRect(29, 46, 16, 16, 5, 5);
		g.drawRoundRect(48, 46, 16, 16, 5, 5);
		shapeTypes[4].flush();
		g.dispose();
		shapeTypes[4] = blur(shapeTypes[4], 5);

		coloredShapeTypes[4] = new BufferedImage(110, 88, BufferedImage.TYPE_INT_ARGB);
		Graphics2D g1 = coloredShapeTypes[4].createGraphics();
		g1.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
		g1.setRenderingHint(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_QUALITY);
		g1.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
		g1.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
		g1.setColor(new Color(180, 240, 250, 60));
		g1.fillRoundRect(44, 22, 22, 26, 8, 8);
		g1.fillRoundRect(66, 22, 22, 26, 8, 8);
		g1.fillRoundRect(22, 40, 27, 26, 8, 8);
		g1.fillRoundRect(44, 40, 27, 26, 8, 8);
		g1.setColor(new Color(250, 50, 50, 50));
		g1.drawRoundRect(48, 27, 16, 16, 5, 5);
		g1.drawRoundRect(67, 27, 16, 16, 5, 5);
		g1.drawRoundRect(29, 46, 16, 16, 5, 5);
		g1.drawRoundRect(48, 46, 16, 16, 5, 5);
		coloredShapeTypes[4].flush();
		g1.dispose();
	}

	private void initAll () {
		initGamePanelBackground();	// should come first
		initBorder();
		initGamePanelContent();
		initBackgroundLetterT();
		initDrops();
		initHomeScreenSurface();
		initFakeFrame();
		initMovieClips();
		initBackgroundFullScreen();
	}

	@Override
	public void paintComponent (Graphics g) {
		super.paintComponent(g);
		Graphics2D g2d = (Graphics2D) g;
		g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
		g2d.setRenderingHint(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_QUALITY);
		g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
		g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);

		g2d.drawImage(backgroundFullScreen, 0, 0, null);
		paintMovieClip(g2d, (int) x, (int) y, backgroundShapeDroppingMovieClip);
		synchronized (this) {
			g2d.drawImage(fakeFrame, (int) x, (int) y, null);
		}
		paintMouseDownImage(g2d);
		paintMouseOverImage(g2d);
		paintControls(g2d);
		synchronized (mouseClickedBlockRoundingMovieClips) {
			for (MovieClip mc : mouseClickedBlockRoundingMovieClips) {
				paintMovieClip(g2d, (int) x, (int) y, mc);
			}
		}
		if (mode != -1) {
			synchronized (tetris) {
				boolean[][] status = tetris.getStatus();
				if (status != null) {
					for (int i = 0; i < backgroundSmall.length; i++) {
						for (int j = 0; j < backgroundSmall[0].length; j++) {
							if (status[i + 1][j + 1]) {
								g2d.setColor(new Color(0, 0, 0, 100));
								g2d.fillRect((int) (x + gamePanelX + 5 + j * unitSize), (int) (y + gamePanelY + 5 + i * unitSize), unitSize, unitSize);
								g2d.drawImage(backgroundSmall[i][j], (int) (x + gamePanelX + 2 + j * unitSize), (int) (y + gamePanelY + 2 + i * unitSize), null);
								g2d.setColor(new Color(255, 255, 255, 80));
								g2d.drawRect((int) (x + gamePanelX + 2 + j * unitSize), (int) (y + gamePanelY + 2 + i * unitSize), unitSize, unitSize);
							}
						}
					}
				}
			}
		}
		synchronized (eliminatingMovieClips) {
			for (MovieClip mc : eliminatingMovieClips) {
				paintMovieClip(g2d, (int) x, (int) y, mc);
			}
		}

		requestFocus();
	}

	private void paintMovieClip (Graphics2D g, int offX, int offY, MovieClip clip) {
		synchronized (clip) {
			g.drawImage(clip, offX + clip.getX(), offY + clip.getY(), null);
		}
	}

	private void paintMouseDownImage (Graphics2D g) {
		switch (down) {
			case 2:	// start button
				g.drawImage(btnStartDownImg, (int) (x + width - 120), (int) (y + height - 120), null);
				break;
		}
	}

	private void paintMouseOverImage (Graphics2D g) {
		switch (over) {
			case 0:	// min button
				g.setColor(new Color(10, 180, 255, 100));
				g.fillRoundRect((int) (x + width - 79), (int) y + 1, 29, 23, 3, 3);
				break;
			case 1:	// close button
				g.setColor(new Color(255, 6, 0, 100));
				g.fillRoundRect((int) (x + width - 49), (int) y + 1, 48, 23, 3, 3);
				break;
			case 2:	// start button
				if (down != over) {
					g.drawImage(btnStartOverImg, (int) (x + width - 120), (int) (y + height - 120), null);
				}
				break;
		}
	}

	private void paintControls (Graphics2D g) {
		g.drawImage(btnMinIcon, (int) (x + width - 71), (int) y + 13, null);
		g.drawImage(btnCloseIcon, (int) (x + width - 33), (int) y + 7, null);
		if (over != 2) {
			g.drawImage(btnStartImg, (int) (x + width - 120), (int) (y + height - 120), null);
		}
		if (clicked != 3 && clicked != 4) {
			g.drawImage(gamePanelContent, (int) x, (int) y, null);
		}
		paintMenuItem(mnuStop, g);
		paintMenuItem(mnuStart, g);
		paintMenuItem(mnuHome, g);
		paintMenuItem(lblTimer, g);
		paintMenuItem(lblScore, g);
		paintMenuItem(lblElim, g);
		paintMenuItem(lblPreview, g);
	}

	private void paintMenuItem (CustomComponent item, Graphics2D g) {
		synchronized (item) {
			if (item.isVisible()) {
				if (item.isEnabled()) {
					if (down != -1 && compMap[down] == item) {
						g.drawImage(item.imgMouseDown, (int) (x + item.getX()), (int) (y + item.getY()), null);
					} else if (over == -1 || compMap[over] != item) {
						g.drawImage(item.imgNormal, (int) (x + item.getX()), (int) (y + item.getY()), null);
					} else {
						g.drawImage(item.imgMouseOver, (int) (x + item.getX()), (int) (y + item.getY()), null);
					}
				} else {
					g.drawImage(item.imgDisabled, (int) (x + item.getX()), (int) (y + item.getY()), null);
				}
			}
		}
	}

	private void paintLabels (Graphics2D g, String[] str, int[] x, int[] y, double[] angle, int[] size) {
		BufferedImage labels = new BufferedImage((int) width, (int) height, BufferedImage.TYPE_INT_ARGB);
		Graphics2D g2d = labels.createGraphics();
		g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
		g2d.setRenderingHint(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_QUALITY);
		g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
		g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);

		g2d.setColor(new Color(0, 60, 80, 220));
		for (int i = 0; i < str.length; i++) {
			g2d.rotate(angle[i]);
			g2d.setFont(new Font("Tahoma", Font.BOLD, size[i]));
			g2d.drawString(str[i], x[i], y[i]);
			g2d.rotate(-angle[i]);
		}
		labels.flush();
		g2d.dispose();
		labels = blur(labels, 20);

		g.drawImage(labels, 0, 0, null);
		g.setColor(new Color(250, 250, 250, 190));
		for (int i = 0; i < str.length; i++) {
			g.rotate(angle[i]);
			g.setFont(new Font("Tahoma", Font.BOLD, size[i]));
			g.drawString(str[i], x[i], y[i]);
			g.rotate(-angle[i]);
		}
	}

	public static BufferedImage blur (BufferedImage originalPic, int radius) {
		int imageWidth = originalPic.getWidth();
		int imageHeight = originalPic.getHeight();

		BufferedImage newPic = new BufferedImage(imageWidth, imageHeight, BufferedImage.TYPE_INT_ARGB);
		BufferedImage newPic2 = new BufferedImage(imageWidth, imageHeight, BufferedImage.TYPE_INT_ARGB);

		float[] data = new float[radius * radius];
		for (int i = 0; i < data.length; i++) {
			data[i] = 1f / (data.length - 1);
		}

		Kernel kernel = new Kernel(radius, radius, data);
		ConvolveOp co = new ConvolveOp(kernel, ConvolveOp.EDGE_NO_OP, new RenderingHints(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC));
		co.filter(originalPic, newPic);
		co.filter(newPic, newPic2);
		return newPic2;
	}

	private void setClipboardImage (final Image image) {
		Transferable trans = new Transferable() {
			@Override
			public DataFlavor[] getTransferDataFlavors () {
				return new DataFlavor[]{DataFlavor.imageFlavor};
			}

			@Override
			public boolean isDataFlavorSupported (DataFlavor flavor) {
				return DataFlavor.imageFlavor.equals(flavor);
			}

			@Override
			public Object getTransferData (DataFlavor flavor) throws UnsupportedFlavorException, IOException {
				if (isDataFlavorSupported(flavor)) {
					return image;
				}
				throw new UnsupportedFlavorException(flavor);
			}

		};
		Toolkit.getDefaultToolkit().getSystemClipboard().setContents(trans, null);
	}

	private void initControls () {
		try {
			btnMinIcon = ImageIO.read(URL_IMG_MIN);
			btnCloseIcon = ImageIO.read(URL_IMG_CLOSE);

			initBtnStart();
			initBtnStartOver();
			initBtnStartDown();

			tetris.setScore(0);
			tetris.setElim(0);

			lblTimer.setVisible(false);
			lblScore.setVisible(false);
			lblElim.setVisible(false);
			lblPreview.setVisible(false);

			mnuHome.setVisible(false);
			mnuStart.setVisible(false);
			mnuStop.setVisible(false);

			mnuHome.setEnabled(false);
			mnuStart.setEnabled(false);
			mnuStop.setEnabled(false);
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	private void initMovieClips () {
		initBackgroundShapeDroppingMovieClip();
	}

	/**
	 * Background shape dropping movie
	 */
	private void initBackgroundShapeDroppingMovieClip () {
		backgroundShapeDroppingMovieClip = new MovieClip(0, 0, (int) width, (int) height, BufferedImage.TYPE_INT_ARGB);
		int num = 7 + rnd.nextInt(3);
		for (int i = 0; i < num; i++) {
			backgroundShapeDroppingMovieClip.add(shapeTypes[rnd.nextInt(5)]);
			backgroundShapeDroppingMovieClip.setRandomImageOptions(i, shapeTypes);
			backgroundShapeDroppingMovieClip.setStartPoint(i, new Point2D.Double((width - 80) * Math.random(), height * Math.random()));
			backgroundShapeDroppingMovieClip.setStartRotation(i, Math.random() * 6.28);
			backgroundShapeDroppingMovieClip.setStartScale(i, 0.7 + Math.random() * 0.6);
			double[] speed = new double[18];
			double[] rotateSpeed = new double[18];
			double baseSpeed = height * (0.3 + 0.3 * Math.random()) / 8000;
			double baseRotateSpeed = 6.28 / 6000;
			for (int j = 0; j < speed.length; j += 2) {
				speed[j] = (0.5 + Math.random()) * baseSpeed;
				speed[j + 1] = 1800 * (1.8 + Math.random());
			}
			for (int j = 0; j < rotateSpeed.length; j += 2) {
				rotateSpeed[j] = (0.2 + Math.random()) * baseRotateSpeed;
				rotateSpeed[j + 1] = 1800 * (1.8 + Math.random());
			}
			backgroundShapeDroppingMovieClip.setSpeed(i, speed);
			backgroundShapeDroppingMovieClip.setRotationSpeed(i, rotateSpeed);
		}
		backgroundShapeDroppingMovieClip.start(-1);	// infinite loop
	}

	private void stopAllMovie () {
		if (backgroundShapeDroppingMovieClip != null) {
			backgroundShapeDroppingMovieClip.stop();
		}
		synchronized (mouseClickedBlockRoundingMovieClips) {
			for (MovieClip mc : mouseClickedBlockRoundingMovieClips) {
				mc.stop();
			}
			mouseClickedBlockRoundingMovieClips.clear();
		}
		synchronized (eliminatingMovieClips) {
			for (MovieClip mc : eliminatingMovieClips) {
				mc.stop();
			}
			eliminatingMovieClips.clear();
		}
	}

	/**
	 * Mouse clicked block dropping movie
	 */
	private void initMouseClickedBlockRoundingMovie (int mouseX, int mouseY, int width, int height) {
		MovieClip mc = new MovieClip(mouseX - width / 2, mouseY - height / 2, width, height, BufferedImage.TYPE_INT_ARGB);
		int num = 15 + rnd.nextInt(3);
		for (int i = 0; i < num; i++) {
			mc.add(coloredShapeTypes[rnd.nextInt(5)]);
			mc.setStartPoint(i, new Point2D.Double(width * (Math.random() * 0.3), height * (Math.random() * 0.3)));
			mc.setStartRotation(i, Math.random() * 6.28);
			mc.setStartScale(i, 0.15 + Math.random() * 0.15);
			double[] speed = new double[6];
			double[] rotateSpeed = new double[6];
			double baseSpeed = height * (0.2 + 0.3 * Math.random()) / 8000;
			double baseRotateSpeed = 6.28 / 2000;
			for (int j = 0; j < speed.length; j += 2) {
				speed[j] = (0.5 + Math.random()) * baseSpeed;
				speed[j + 1] = 100 * (0.3 + 8 * Math.random());
			}
			for (int j = 0; j < rotateSpeed.length; j += 2) {
				rotateSpeed[j] = (0.2 + Math.random()) * baseRotateSpeed;
				rotateSpeed[j + 1] = 100 * (0.3 + 8 * Math.random());
			}
			mc.setSpeed(i, speed);
			mc.setRotationSpeed(i, rotateSpeed);
		}
		mouseClickedBlockRoundingMovieClips.add(mc);
		mc.start(1);
	}

	/**
	 * Eliminating animation
	 */
	public void initEliminationMovie () {
		MovieClip mc = new MovieClip(0, 0, (int) width, (int) height, BufferedImage.TYPE_INT_ARGB);
		int[] eliminated = tetris.getEliminatedRows();
		int n = 0;
		for (int e : eliminated) {
			if (e != -1) {
				for (int i = 0; i < cols; i++, n++) {
					mc.add(backgroundSmallTranscluent[e][i]);
					mc.setStartPoint(n, new Point2D.Double(gamePanelX + 5 + i * unitSize, gamePanelY + 5 + e * unitSize));
					mc.setStartRotation(n, 0);
					mc.setStartScale(n, 1);
					double[] speed = new double[6];
					double[] rotateSpeed = new double[6];
					double baseSpeed = height * (0.2 + 0.3 * Math.random()) / 4000;
					double baseRotateSpeed = 6.28 / 2000;
					for (int j = 0; j < speed.length; j += 2) {
						speed[j] = (0.5 + Math.random()) * baseSpeed;
						speed[j + 1] = 500 * (0.3 + 8 * Math.random());
					}
					for (int j = 0; j < rotateSpeed.length; j += 2) {
						rotateSpeed[j] = (0.2 + Math.random()) * baseRotateSpeed;
						rotateSpeed[j + 1] = 500 * (0.3 + 8 * Math.random());
					}
					mc.setSpeed(n, speed);
					mc.setRotationSpeed(n, rotateSpeed);
				}
			}
		}
		eliminatingMovieClips.add(mc);
		mc.start(1);
	}

	private void initBtnStartIcon () {
		btnStartIcon = new BufferedImage(110, 88, BufferedImage.TYPE_INT_ARGB);
		Graphics2D g = btnStartIcon.createGraphics();
		g.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
		g.setRenderingHint(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_QUALITY);
		g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
		g.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);

		g.setColor(new Color(250, 250, 250, 90));
		g.fillRoundRect(22, 22, 22, 26, 8, 8);
		g.fillRoundRect(44, 22, 22, 26, 8, 8);
		g.fillRoundRect(66, 22, 22, 26, 8, 8);
		g.fillRoundRect(22, 40, 27, 26, 8, 8);
		btnStartIcon.flush();
		g.dispose();
		btnStartIcon = blur(btnStartIcon, 10);

		g = btnStartIcon.createGraphics();
		g.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
		g.setRenderingHint(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_QUALITY);
		g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
		g.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);

		g.setColor(new Color(250, 250, 250, 60));
		g.fillRoundRect(29, 27, 16, 16, 5, 5);
		g.fillRoundRect(48, 27, 16, 16, 5, 5);
		g.fillRoundRect(67, 27, 16, 16, 5, 5);
		g.fillRoundRect(29, 46, 16, 16, 5, 5);
		g.setColor(new Color(50, 50, 55, 220));
		g.drawRoundRect(29, 27, 16, 16, 5, 5);
		g.drawRoundRect(48, 27, 16, 16, 5, 5);
		g.drawRoundRect(67, 27, 16, 16, 5, 5);
		g.drawRoundRect(29, 46, 16, 16, 5, 5);
		btnStartIcon.flush();
		g.dispose();
	}

	private void initBtnStartIconOver () {
		// over icon
		btnStartIconOver = new BufferedImage(110, 88, BufferedImage.TYPE_INT_ARGB);
		Graphics2D g = btnStartIconOver.createGraphics();
		g.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
		g.setRenderingHint(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_QUALITY);
		g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
		g.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);

		g.setColor(new Color(250, 100, 0, 100));
		g.fillRoundRect(22, 22, 22, 26, 8, 8);
		g.fillRoundRect(44, 22, 22, 26, 8, 8);
		g.fillRoundRect(66, 22, 22, 26, 8, 8);
		g.fillRoundRect(22, 40, 27, 26, 8, 8);

		btnStartIconOver.flush();
		g.dispose();
		btnStartIconOver = blur(btnStartIconOver, 10);

		g = btnStartIconOver.createGraphics();
		g.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
		g.setRenderingHint(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_QUALITY);
		g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
		g.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);

		g.setColor(new Color(250, 250, 250, 120));
		g.fillRoundRect(29, 27, 16, 16, 5, 5);
		g.fillRoundRect(48, 27, 16, 16, 5, 5);
		g.fillRoundRect(67, 27, 16, 16, 5, 5);
		g.fillRoundRect(29, 46, 16, 16, 5, 5);
		g.setColor(new Color(150, 150, 205, 220));
		g.drawRoundRect(29, 27, 16, 16, 5, 5);
		g.drawRoundRect(48, 27, 16, 16, 5, 5);
		g.drawRoundRect(67, 27, 16, 16, 5, 5);
		g.drawRoundRect(29, 46, 16, 16, 5, 5);
		btnStartIconOver.flush();
		g.dispose();
	}

	private void initBtnStart () {
		initBtnStartIcon();
		btnStartImg = new BufferedImage(100, 100, BufferedImage.TYPE_INT_ARGB);
		Graphics2D g = btnStartImg.createGraphics();
		g.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
		g.setRenderingHint(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_QUALITY);
		g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
		g.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);

		Ellipse2D.Float btnStartOutline = new Ellipse2D.Float(0, 0, btnStartImg.getWidth() - 1, btnStartImg.getHeight() - 1);
		Ellipse2D.Float btnStart2 = new Ellipse2D.Float(-60, -100, 160, 160);
		// draw icon:
		g.rotate(-3.14 / 2.5);
		g.drawImage(btnStartIcon, -88, 20, null);
		g.rotate(3.14 / 2.5);
		// draw border:
		Area a = new Area(btnStartOutline);
		a.intersect(new Area(btnStart2));
		GradientPaint p = new GradientPaint(0, 120, new Color(0, 30, 50, 70), 100, 50, new Color(0, 20, 30, 20));
		g.setPaint(p);
		g.fill(btnStartOutline);
		p = new GradientPaint(12, 10, new Color(255, 255, 255, 130), 35, 65, new Color(255, 255, 255, 10));
		g.setPaint(p);
		g.fill(a);
		g.setColor(new Color(255, 250, 250, 130));
		g.drawOval(1, 1, btnStartImg.getWidth() - 3, btnStartImg.getHeight() - 3);
		g.setColor(new Color(55, 50, 50, 180));
		g.draw(btnStartOutline);

		btnStartImg.flush();
		g.dispose();
	}

	private void initBtnStartOver () {
		initBtnStartIconOver();
		btnStartOverImg = new BufferedImage(100, 100, BufferedImage.TYPE_INT_ARGB);
		Graphics2D g = btnStartOverImg.createGraphics();
		g.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
		g.setRenderingHint(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_QUALITY);
		g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
		g.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);

		Ellipse2D.Float btnStartOutline = new Ellipse2D.Float(0, 0, btnStartOverImg.getWidth() - 1, btnStartOverImg.getHeight() - 1);
		Ellipse2D.Float btnStart2 = new Ellipse2D.Float(-60, -100, 160, 160);
		// draw icon:
		g.rotate(-3.14 / 2.5);
		g.drawImage(btnStartIconOver, -88, 20, null);
		g.rotate(3.14 / 2.5);
		// draw border:
		Area a = new Area(btnStartOutline);
		a.intersect(new Area(btnStart2));
		GradientPaint p = new GradientPaint(0, 120, new Color(140, 180, 190, 60), 100, 50, new Color(140, 180, 190, 20));
		g.setPaint(p);
		g.fill(btnStartOutline);
		p = new GradientPaint(10, 10, new Color(255, 255, 255, 150), 35, 65, new Color(255, 255, 255, 10));
		g.setPaint(p);
		g.fill(a);
		g.setColor(new Color(255, 250, 250, 130));
		g.drawOval(1, 1, btnStartOverImg.getWidth() - 3, btnStartOverImg.getHeight() - 3);
		g.setColor(new Color(55, 50, 50, 180));
		g.draw(btnStartOutline);

		btnStartOverImg.flush();
		g.dispose();
	}

	private void initBtnStartDown () {
		if (btnStartIconOver == null) {
			initBtnStartIconOver();
		}
		btnStartDownImg = new BufferedImage(100, 100, BufferedImage.TYPE_INT_ARGB);
		Graphics2D g = btnStartDownImg.createGraphics();
		g.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
		g.setRenderingHint(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_QUALITY);
		g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
		g.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);

		Ellipse2D.Float btnStartOutline = new Ellipse2D.Float(0, 0, btnStartDownImg.getWidth() - 1, btnStartDownImg.getHeight() - 1);
		Ellipse2D.Float btnStart2 = new Ellipse2D.Float(-60, -100, 160, 160);
		// draw icon:
		g.rotate(-3.14 / 2.5);
		g.drawImage(btnStartIconOver, -89, 22, null);
		g.rotate(3.14 / 2.5);
		// draw border:
		Area a = new Area(btnStartOutline);
		a.intersect(new Area(btnStart2));
		GradientPaint p = new GradientPaint(0, 120, new Color(140, 180, 190, 150), 100, 50, new Color(140, 180, 190, 70));
		g.setPaint(p);
		g.fill(btnStartOutline);
		p = new GradientPaint(10, 10, new Color(255, 255, 255, 200), 35, 65, new Color(255, 255, 255, 20));
		g.setPaint(p);
		g.fill(a);
		g.setColor(new Color(255, 250, 250, 130));
		g.drawOval(1, 1, btnStartDownImg.getWidth() - 3, btnStartDownImg.getHeight() - 3);
		g.setColor(new Color(55, 50, 50, 180));
		g.draw(btnStartOutline);

		btnStartDownImg.flush();
		g.dispose();
	}

	private void initGamePanelBackground () {
		synchronized (this) {
			boolean first = false;
			float width, height, w2h = (float) backgroundOrigin.getWidth() / backgroundOrigin.getHeight();
			float screen_w2h = (float) Constants.SCREEN_SIZE.width / Constants.SCREEN_SIZE.height;
			if (w2h < screen_w2h) {
				height = Constants.SCREEN_SIZE.height * 0.6f;
				width = height * w2h;
			} else {
				width = Constants.SCREEN_SIZE.width * 0.6f;
				height = width / w2h;
			}

			rows = (int) (height / unitSize);
			cols = (int) (width / unitSize);

			height = rows * unitSize;
			width = cols * unitSize;
			if (this.width == 0) {
				first = true;
			}
			this.width = width + 204;
			this.height = height + 84;

			gamePanelWidth = (int) width + 7;
			gamePanelHeight = (int) height + 7;
			gamePanelX = 20;
			gamePanelY = (int) this.height - gamePanelHeight - 20;
			if (first) {
				x = (Constants.SCREEN_SIZE.width - this.width) / 2;
				y = (Constants.SCREEN_SIZE.height - this.height) / 2;
			}
			updateLocations();

			BufferedImage backgroundSmallWhole = new BufferedImage((int) width, (int) height, BufferedImage.TYPE_INT_RGB);
			Graphics2D g = backgroundSmallWhole.createGraphics();
			g.drawImage(backgroundOrigin, 0, 0, (int) width, (int) height, null);
			g.dispose();

			backgroundSmall = new BufferedImage[rows][cols];
			for (int i = 0; i < rows; i++) {
				for (int j = 0; j < cols; j++) {
					backgroundSmall[i][j] = backgroundSmallWhole.getSubimage(j * unitSize, i * unitSize, unitSize, unitSize);
				}
			}

			backgroundSmallTranscluent = new BufferedImage[rows][cols];
			for (int i = 0; i < rows; i++) {
				for (int j = 0; j < cols; j++) {
					BufferedImage img = backgroundSmallWhole.getSubimage(j * unitSize, i * unitSize, unitSize, unitSize);
					backgroundSmallTranscluent[i][j] = new BufferedImage(unitSize, unitSize, BufferedImage.TYPE_INT_ARGB);
					Graphics2D g2d = backgroundSmallTranscluent[i][j].createGraphics();
					for (int p = 0; p < unitSize; p++) {
						for (int q = 0; q < unitSize; q++) {
							int rgb = img.getRGB(q, p);
							g2d.setColor(new Color(rgb >>> 16 & 0xff, rgb >> 8 & 0xff, rgb & 0xff, 80));
							g2d.drawLine(q, p, q, p);
						}
					}
					backgroundSmallTranscluent[i][j].flush();
					g2d.dispose();
				}
			}
		}
		parent.setFakeFrameShape(x, y, width, height, x + gamePanelX, y + gamePanelY, gamePanelWidth, gamePanelHeight, gamePanelPaddingX, gamePanelPaddingY);
	}

	private void initBorder () {
		border = new BufferedImage((int) width, (int) height, BufferedImage.TYPE_INT_ARGB);
		Graphics2D g2d = border.createGraphics();
		g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
		g2d.setRenderingHint(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_QUALITY);
		g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
		g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);

		g2d.setColor(Constants.BACKGROUND_BORDER_LIGHT);
		g2d.drawRoundRect(1, 1, (int) width - 3, (int) height - 3, (int) parent.getFakeBounds().arcwidth, (int) parent.getFakeBounds().archeight);
		g2d.drawRoundRect((int) width - 79, -9, 80, 31, 8, 8);
		g2d.drawLine((int) width - 49, -9, (int) width - 49, 22);
		g2d.drawRoundRect(gamePanelX - gamePanelPaddingX - 2, gamePanelY - gamePanelPaddingY - 2, gamePanelWidth + gamePanelPaddingX * 2 + 3, gamePanelHeight + gamePanelPaddingY * 2 + 3, (int) parent.getGamePanelBoundsOut().arcwidth, (int) parent.getGamePanelBoundsOut().archeight);

		g2d.setColor(new Color(255, 255, 255, 140));
		g2d.drawRoundRect((int) width - 81, -9, 80, 33, 8, 8);
		g2d.drawLine((int) width - 51, -9, (int) width - 51, 22);

		g2d.setColor(Color.GRAY);
		g2d.drawRoundRect(0, 0, (int) width - 1, (int) height - 1, (int) parent.getFakeBounds().arcwidth, (int) parent.getFakeBounds().archeight);
		g2d.drawRoundRect((int) width - 80, -9, 80, 32, 8, 8);
		g2d.drawLine((int) width - 50, -9, (int) width - 50, 22);
		g2d.drawRoundRect(gamePanelX - gamePanelPaddingX - 1, gamePanelY - gamePanelPaddingY - 1, gamePanelWidth + gamePanelPaddingX * 2 + 1, gamePanelHeight + gamePanelPaddingY * 2 + 1, (int) parent.getGamePanelBoundsOut().arcwidth, (int) parent.getGamePanelBoundsOut().archeight);
		g2d.setColor(new Color(160, 160, 160));
		g2d.drawRoundRect(gamePanelX, gamePanelY, gamePanelWidth - 1, gamePanelHeight - 1, (int) parent.getGamePanelBoundsOut().arcwidth, (int) parent.getGamePanelBoundsOut().archeight);

		g2d.setColor(Constants.BACKGROUND_COVER);
		g2d.fillRect(0, 0, Constants.SCREEN_SIZE.width, Constants.SCREEN_SIZE.height);

		// draw game panel border light
		g2d.setColor(new Color(250, 250, 250, 190));
		g2d.drawRoundRect(gamePanelX + 1, gamePanelY + 1, gamePanelWidth - 3, gamePanelHeight - 3, (int) parent.getGamePanelBoundsOut().arcwidth, (int) parent.getGamePanelBoundsOut().archeight);

		border.flush();
		g2d.dispose();
	}

	private void initGamePanelContent () {
		gamePanelContent = new BufferedImage((int) width, (int) height, BufferedImage.TYPE_INT_ARGB);
		Graphics2D g2d = gamePanelContent.createGraphics();
		g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
		g2d.setRenderingHint(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_QUALITY);
		g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
		g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);

		// draw round button edges:
		// classic button:
		g2d.setColor(new Color(0, 0, 0, 30));
		g2d.drawOval(gamePanelX + 35, gamePanelY + 35, 240, 240);
		g2d.setColor(new Color(0, 0, 0, 30));
		g2d.drawOval(gamePanelX + 36, gamePanelY + 36, 239, 239);
		g2d.setColor(new Color(0, 0, 0, 20));
		g2d.drawOval(gamePanelX + 37, gamePanelY + 37, 239, 239);
		g2d.drawOval(gamePanelX + 33, gamePanelY + 33, 242, 242);
		g2d.setColor(new Color(0, 0, 0, 20));
		g2d.drawOval(gamePanelX + 38, gamePanelY + 38, 238, 238);
		g2d.setColor(new Color(0, 0, 0, 10));
		g2d.drawOval(gamePanelX + 39, gamePanelY + 39, 237, 237);
		g2d.setColor(new Color(250, 250, 250, 80));
		g2d.drawOval(gamePanelX + 33, gamePanelY + 33, 242, 242);
		g2d.drawOval(gamePanelX + 34, gamePanelY + 34, 242, 242);
		g2d.drawOval(gamePanelX + 34, gamePanelY + 34, 243, 243);
		// challenge button:
		g2d.setColor(new Color(0, 0, 0, 30));
		g2d.drawOval(gamePanelX + 285, gamePanelY + 150, 280, 280);
		g2d.setColor(new Color(0, 0, 0, 30));
		g2d.drawOval(gamePanelX + 286, gamePanelY + 151, 279, 279);
		g2d.setColor(new Color(0, 0, 0, 20));
		g2d.drawOval(gamePanelX + 287, gamePanelY + 152, 279, 279);
		g2d.drawOval(gamePanelX + 283, gamePanelY + 148, 282, 282);
		g2d.setColor(new Color(0, 0, 0, 20));
		g2d.drawOval(gamePanelX + 287, gamePanelY + 152, 278, 278);
		g2d.setColor(new Color(0, 0, 0, 10));
		g2d.drawOval(gamePanelX + 288, gamePanelY + 153, 277, 277);
		g2d.setColor(new Color(250, 250, 250, 80));
		g2d.drawOval(gamePanelX + 283, gamePanelY + 148, 282, 282);
		g2d.drawOval(gamePanelX + 284, gamePanelY + 149, 282, 282);
		g2d.drawOval(gamePanelX + 284, gamePanelY + 149, 283, 283);

		// draw labels:
		paintLabels(g2d, new String[]{"Classic", "Challenge"}, new int[]{gamePanelX + 121, gamePanelX + 255}, new int[]{gamePanelY + 130, gamePanelY + 370}, new double[]{3.14 / 15, -3.14 / 20}, new int[]{43, 45});

		gamePanelContent.flush();
		g2d.dispose();
	}

	private void initDrops () {
		backgroundDots = new boolean[(int) height][(int) width];
		for (int i = 0; i < backgroundDots.length; i++) {
			for (int j = 0; j < backgroundDots[0].length; j++) {
				backgroundDots[i][j] = Math.random() > 0.9997;
			}
		}

		drops = new BufferedImage((int) width, (int) height, BufferedImage.TYPE_INT_ARGB);
		Graphics2D g2d = drops.createGraphics();
		g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
		g2d.setRenderingHint(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_QUALITY);
		g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
		g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);

		g2d.setColor(Constants.BACKGROUND_DOT_SHADOW);
		for (int i = 0; i < backgroundDots.length; i++) {
			for (int j = 0; j < backgroundDots[0].length; j++) {
				if (backgroundDots[i][j]) {
					g2d.fillOval(j + 4, i + 4, 4 + rnd.nextInt(9), 6 + rnd.nextInt(9));
				}
			}
		}
		g2d.setColor(Constants.BACKGROUND_DOT);
		for (int i = 0; i < backgroundDots.length; i++) {
			for (int j = 0; j < backgroundDots[0].length; j++) {
				if (backgroundDots[i][j]) {
					g2d.fillOval(j, i, 6 + rnd.nextInt(9), 6 + rnd.nextInt(9));
				}
			}
		}
		drops.flush();
		g2d.dispose();
		drops = blur(drops, 3 + rnd.nextInt(3));
	}

	private void initHomeScreenSurface () {
		homeScreenSurface = new BufferedImage((int) width, (int) height, BufferedImage.TYPE_INT_ARGB);
		Graphics2D g2d = homeScreenSurface.createGraphics();
		g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
		g2d.setRenderingHint(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_QUALITY);
		g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
		g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);

		homeScreenPath.reset();
		homeScreenPath.setWindingRule(Path2D.WIND_EVEN_ODD);
		RoundRectangle2D.Float inner = new RoundRectangle2D.Float(gamePanelX, gamePanelY, gamePanelWidth, gamePanelHeight, parent.getGamePanelBoundsOut().arcwidth, parent.getGamePanelBoundsOut().archeight);
		Ellipse2D btnClassic = new Ellipse2D.Float(gamePanelX + 35, gamePanelY + 35, this.btnClassic.width, this.btnClassic.height);
		Ellipse2D btnChallenge = new Ellipse2D.Float(gamePanelX + 285, gamePanelY + 150, this.btnChallenge.width, this.btnChallenge.height);
		TextLayout txtTetris = new TextLayout("Tetris", new Font("Tahoma", Font.BOLD, 52), g2d.getFontRenderContext());
		TextLayout txtQYZ = new TextLayout("By QYZ", new Font("Tahoma", Font.BOLD, 42), g2d.getFontRenderContext());

		homeScreenPath.append(inner, false);
		homeScreenPath.append(btnClassic, false);
		homeScreenPath.append(btnChallenge, false);
		Shape sTxtTetris = txtTetris.getOutline(g2d.getTransform());
		Shape sTxtQYZ = txtQYZ.getOutline(g2d.getTransform());
		appendTextShape(homeScreenPath, sTxtTetris, g2d.getTransform(), gamePanelX + gamePanelWidth - sTxtTetris.getBounds().width - 30, gamePanelY + 65);
		appendTextShape(homeScreenPath, sTxtQYZ, g2d.getTransform(), gamePanelX + 30, gamePanelY + gamePanelHeight - sTxtQYZ.getBounds().height + 5);

		spreadingCover = new SpreadingCover(gamePanelX + 20, gamePanelY + 20, gamePanelWidth - 40, gamePanelHeight - 40, homeScreenPath);

		textShadows = new BufferedImage((int) width, (int) height, BufferedImage.TYPE_INT_ARGB);
		Graphics2D g = textShadows.createGraphics();
		g.setBackground(new Color(255, 255, 255, 0));
		g.clearRect(0, 0, (int) width, (int) height);
		g.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
		g.setRenderingHint(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_QUALITY);
		g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
		g.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);

		g.setColor(new Color(255, 255, 255, 250));
		g.translate(gamePanelX + gamePanelWidth - sTxtTetris.getBounds().width - 30, gamePanelY + 65);
		g.fill(sTxtTetris);
		g.translate(sTxtTetris.getBounds().width - gamePanelWidth + 60, gamePanelHeight - sTxtTetris.getBounds().height - 60);
		g.fill(sTxtQYZ);
		textShadows.flush();
		g.dispose();
		textShadows = blur(textShadows, 15);

		g2d.dispose();
		paintHomeScreenSurface();
	}

	private void paintHomeScreenSurface () {
		homeScreenSurface = new BufferedImage((int) width, (int) height, BufferedImage.TYPE_INT_ARGB);
		Graphics2D g2d = homeScreenSurface.createGraphics();
		g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
		g2d.setRenderingHint(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_QUALITY);
		g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
		g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);

		g2d.setColor(new Color(250, 250, 250, 190));
		synchronized (spreadingCover) {
			g2d.fill(spreadingCover);
		}
		g2d.setClip(spreadingCover);
		if (clicked != 3 && clicked != 4) {
			g2d.drawImage(textShadows, 0, 0, null);
		}

		homeScreenSurface.flush();
		g2d.dispose();
	}

	private void initBackgroundLetterT () {
		backgroundLetterT = new BufferedImage((int) width, (int) height, BufferedImage.TYPE_INT_ARGB);
		Graphics2D g2d = backgroundLetterT.createGraphics();
		g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
		g2d.setRenderingHint(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_QUALITY);
		g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
		g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);

		g2d.setColor(new Color(255, 255, 250, 30));
		for (int i = 0; i < width; i += 100) {
			for (int j = 0; j < height; j += 100) {
				if (Math.random() < 0.25) {
					double angle = 6.28 * Math.random();
					g2d.setFont(new Font("Tahoma", Font.BOLD, 200 + rnd.nextInt(300)));

					g2d.translate(i, j);
					g2d.rotate(angle, 20, 20);
					g2d.drawString("T", 0, 0);
					g2d.rotate(-angle, 20, 20);
					g2d.translate(-i, -j);
				}
			}
		}

		backgroundLetterT.flush();
		g2d.dispose();
	}

	private synchronized void initFakeFrame () {
		// merge fake frame:
		fakeFrame = new BufferedImage((int) width, (int) height, BufferedImage.TYPE_INT_ARGB);
		Graphics2D g2d = fakeFrame.createGraphics();
		g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
		g2d.setRenderingHint(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_QUALITY);
		g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
		g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);

		g2d.drawImage(backgroundLetterT, 0, 0, null);
		g2d.drawImage(drops, 0, 0, null);

		g2d.drawImage(homeScreenSurface, 0, 0, null);
		g2d.drawImage(border, 0, 0, null);

		fakeFrame.flush();
		g2d.dispose();
	}

	/**
	 * 
	 * @param p	The shape appending to
	 * @param s	The shape to append
	 * @param t
	 * @param x
	 * @param y
	 */
	public synchronized static void appendTextShape (Path2D p, Shape s, AffineTransform t, int x, int y) {
		synchronized (s) {
			PathIterator pit = s.getPathIterator(t);
			float[] coords = new float[6];
			int c = 0;
			while (true) {
				pit.next();
				if (pit.isDone()) {
					break;
				}
				switch (pit.currentSegment(coords)) {
					case PathIterator.SEG_MOVETO:
						p.moveTo(x + coords[0], y + coords[1]);
						break;
					case PathIterator.SEG_LINETO:
						if (c == 0) {
							p.moveTo(x + coords[0], y + coords[1]);
						} else {
							p.lineTo(x + coords[0], y + coords[1]);
						}
						break;
					case PathIterator.SEG_QUADTO:
						if (c == 0) {
							p.moveTo(x + coords[0], y + coords[1]);
						} else {
							p.quadTo(x + coords[0], y + coords[1], x + coords[2], y + coords[3]);
						}
						break;
					case PathIterator.SEG_CUBICTO:
						if (c == 0) {
							p.moveTo(x + coords[0], y + coords[1]);
						} else {
							p.curveTo(x + coords[0], y + coords[1], x + coords[2], y + coords[3], x + coords[4], y + coords[5]);
						}
						break;
					case PathIterator.SEG_CLOSE:
						p.closePath();
						break;
				}
				c++;
			}
		}
	}

	private void initBackgroundFullScreen () {
		new Thread() {
			@Override
			public void run () {
				int m = (int) (Math.max(Constants.SCREEN_SIZE.width, Constants.SCREEN_SIZE.height) * 1.4);

				backgroundFullScreen = new BufferedImage(Constants.SCREEN_SIZE.width, Constants.SCREEN_SIZE.height, BufferedImage.TYPE_INT_RGB);
				Graphics2D g = backgroundFullScreen.createGraphics();
				g.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
				g.setRenderingHint(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_QUALITY);
				g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
				g.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);

				g.drawImage(backgroundOrigin, 0, 0, Constants.SCREEN_SIZE.width, Constants.SCREEN_SIZE.height, null);
				g.rotate(-3.14 / 6);
				g.setColor(Constants.BACKGROUND_LIGHT);
				int p = 0;
				while (p < m) {
					int i = 80 + rnd.nextInt(100);
					p += i;
					int j = 30 + rnd.nextInt(150);
					g.fillRect(p, 0, j, m);
					p += j;
				}
				backgroundFullScreen.flush();
				g.dispose();
				backgroundFullScreen = blur(backgroundFullScreen, 15);
				repaint();
			}
		}.start();
	}

	private void updateLocations () {
		btnMin.setLocation((int) (width - 80), 0);
		btnClose.setLocation((int) (width - 50), 0);
		btnStart.setLocation((int) (width - 120), (int) (height - 120));
		btnClassic.setLocation(gamePanelX + 35, gamePanelY + 35);
		btnChallenge.setLocation(gamePanelX + 285, gamePanelY + 150);

		lblTimer.setLocation((int) (width - 140), (int) (height - 490));
		lblScore.setLocation((int) (width - 140), (int) (height - 450));
		lblElim.setLocation((int) (width - 140), (int) (height - 410));
		lblPreview.setLocation((int) (width - 140), (int) (height - 370));

		mnuStart.setLocation((int) (width - 140), (int) (height - 160));
		mnuStop.setLocation((int) (width - 140), (int) (height - 200));
		mnuHome.setLocation((int) (width - 140), (int) (height - 240));
	}

	public int getRows () {
		return rows;
	}

	public int getCols () {
		return cols;
	}

	void closeRecorder () {
		tetris.closeRecorder();
	}

	private void closeFakeFrame () {
		parent.dispatchEvent(new WindowEvent(parent, WindowEvent.WINDOW_CLOSING));
	}

	private void minimizeFakeFrame () {
		parent.setState(Frame.ICONIFIED);
		over = -1;
	}

	private void startSpreadingAnimation () {
		if (thdRepaintHomeScreenSurface == null || thdRepaintHomeScreenSurface.getState().equals(Thread.State.TERMINATED)) {
			thdRepaintHomeScreenSurface = new Thread() {
				@Override
				public void run () {
					while (!spreadingCover.isStopped()) {
						paintHomeScreenSurface();
						initFakeFrame();
						try {
							Thread.sleep(30);
						} catch (InterruptedException e) {
							e.printStackTrace();
						}
					}
					paintHomeScreenSurface();
					initFakeFrame();
					mnuHome.setEnabled(true);
					mnuStart.setEnabled(true);
					lblTimer.setVisible(true);
					lblScore.setVisible(true);
					lblElim.setVisible(true);
					lblPreview.setVisible(true);
				}
			};
			spreadingCover.start();
			thdRepaintHomeScreenSurface.start();
		}
	}

	public void setScore (int score, int highest) {
		synchronized (lblScore) {
			lblScore.setLabel(score + "/" + highest);
		}
		repaint();
	}

	public void setElim (int elim, int highest) {
		synchronized (lblElim) {
			lblElim.setLabel(elim + "/" + highest);
		}
		repaint();
	}

	public void setPreview (int[][] block) {
		Path2D preview = new Path2D.Float();
		if (block != null) {
			preview.setWindingRule(Path2D.WIND_EVEN_ODD);

			int w = 0, h = 0, unitWidth = 24;
			for (int[] element : block) {
				if (element[0] > h) {
					h = element[0];
				}
				if (element[1] > w) {
					w = element[1];
				}
			}

			for (int[] element : block) {
				int x = 61 - (w + 1) * (unitWidth / 2 + 1);
				int y = 61 - (h + 1) * (unitWidth / 2 + 1);
				preview.append(new RoundRectangle2D.Float(x + element[1] * (unitWidth + 2), y + element[0] * (unitWidth + 2), unitWidth, unitWidth, 5, 5), false);
			}
		}
		lblPreview.setIcon(preview);
		repaint();
	}

	private void startTetris () {
		if (thdTetris != null && thdTetris.isAlive()) {
			return;
		}
		mnuStart.setLabel("Pause");
		tetris.newGame();
		thdTetris = new Thread(tetris);
		thdTetris.start();
		mnuStop.setEnabled(true);
	}

	private void stopTetris () {
		new Thread() {
			@Override
			public void run () {
				tetris.stop();
				mnuStop.setEnabled(false);
				while (thdTetris.isAlive()) {
					try {
						Thread.sleep(1);
					} catch (InterruptedException e1) {
						e1.printStackTrace();
					}
				}
				tetris.reset();
				mnuStart.setLabel("Start");
			}
		}.start();
	}

	public void tetrisNormallyGameOver () {
		mnuStop.setEnabled(false);
		mnuStart.setLabel("Start");
	}

	public void tetrisPaused () {
		mnuStart.setLabel("Resume");
		repaint();
	}

	public void tetrisResumed () {
		mnuStart.setLabel("Pause");
		repaint();
	}

	@Override
	public void mouseClicked (MouseEvent e) {
		int oldClicked = clicked;
		switch (over) {
			case 0:	//minimize
				minimizeFakeFrame();
				return;
			case 1:	//close
				closeFakeFrame();
				return;
			case 2:	// start button
				mnuHome.setVisible(!mnuHome.isVisible());
				mnuStart.setVisible(!mnuStart.isVisible());
				mnuStop.setVisible(!mnuStop.isVisible());
				break;
			case 3:	// classic
				if (clicked == -1) {
					clicked = 3;
					mode = MODE_CLASSIC;
					startSpreadingAnimation();
				}
				break;
			case 4:	// challenge
				if (clicked == -1) {
					clicked = 4;
					mode = MODE_CHALLENGE;
					startSpreadingAnimation();
				}
				break;
			case 5:	// home
				if (clicked != -1) {
					new Thread() {
						@Override
						public void run () {
							clicked = -1;
							mode = -1;
							if (thdTetris != null && thdTetris.isAlive()) {
								stopTetris();
							}
							mnuHome.setEnabled(false);
							mnuStart.setEnabled(false);
							mnuStop.setEnabled(false);
							lblTimer.setVisible(false);
							lblScore.setVisible(false);
							lblElim.setVisible(false);
							lblPreview.setVisible(false);
							initHomeScreenSurface();
							initFakeFrame();
						}
					}.start();
				}
				break;
			case 6:	// start tetris
				if (mnuStart.getLabel().equals("Start")) {
					if (thdTetris != null && thdTetris.isAlive()) {
						stopTetris();
					}
					startTetris();
				} else {
					tetris.changePausedEasy();
					mnuStart.setLabel(tetris.isPaused() ? "Resume" : "Pause");
				}
				break;
			case 7:	// stop tetris
				stopTetris();
				break;
			default:
				if (mode != -1 && tetris != null && tetris.isStarted()) {
					if (e.getButton() == MouseEvent.BUTTON3) {
						tetris.rotateClockwise();
					} else if (e.getButton() == MouseEvent.BUTTON2) {
						tetris.drop();
					} else if (e.getButton() == MouseEvent.BUTTON1) {
						tetris.shiftDown();
					}
				}
				break;
		}
		if (oldClicked != clicked) {
			initFakeFrame();
		}
	}

	@Override
	public void mousePressed (MouseEvent e) {
		// TODO Auto-generated method stub
		mouseX = (int) (e.getX() - x);
		mouseY = (int) (e.getY() - y);
		initMouseClickedBlockRoundingMovie(mouseX, mouseY, 180, 180);
		down = over;
		if (down != -1) {
			compMap[down].mouseClicked();
		}
	}

	@Override
	public void mouseWheelMoved (MouseWheelEvent e) {
		// TODO Auto-generated method stub
		if (mode != -1 && tetris != null && tetris.isStarted()) {
			if (e.getWheelRotation() < 0) {
				tetris.shiftLeft(1);
			} else {
				tetris.shiftRight(1);
			}
		}
	}

	@Override
	public void mouseReleased (MouseEvent e) {
		// TODO Auto-generated method stub
		down = -1;
	}

	@Override
	public void mouseEntered (MouseEvent e) {
		// TODO Auto-generated method stub

	}

	@Override
	public void mouseExited (MouseEvent e) {
		// TODO Auto-generated method stub
		if (down == -1) {
			over = -1;
			repaint();
		}
	}

	@Override
	public void mouseDragged (MouseEvent e) {
		// TODO Auto-generated method stub
		if (over == -1 || over == 3 || over == 4) {
			x = e.getX() - mouseX;
			y = e.getY() - mouseY;
			parent.setFakeFrameShape(x, y, width, height, x + gamePanelX, y + gamePanelY, gamePanelWidth, gamePanelHeight, gamePanelPaddingX, gamePanelPaddingY);
			repaint();
		}
	}

	@Override
	public void mouseMoved (MouseEvent e) {
		// TODO Auto-generated method stub
		Point p = e.getPoint();
		p.translate((int) -x, (int) -y);
		if (btnMin.isEnabled() && btnMin.contains(p)) {
			over = 0;
		} else if (btnClose.isEnabled() && btnClose.contains(p)) {
			over = 1;
		} else if (btnStart.isEnabled() && btnStart.contains(p)) {
			over = 2;
		} else if (btnClassic.isEnabled() && mode == -1 && btnClassic.contains(p)) {
			over = 3;
		} else if (btnChallenge.isEnabled() && mode == -1 && btnChallenge.contains(p)) {
			over = 4;
		} else if (mnuHome.isVisible() && mnuHome.isEnabled() && mnuHome.contains(p)) {
			over = 5;
		} else if (mnuStart.isVisible() && mnuStart.isEnabled() && mnuStart.contains(p)) {
			over = 6;
		} else if (mnuStop.isVisible() && mnuStop.isEnabled() && mnuStop.contains(p)) {
			over = 7;
		} else {
			over = -1;
		}
		repaint();
	}

	@Override
	public void keyTyped (KeyEvent e) {
		// TODO Auto-generated method stub
	}

	@Override
	public void keyPressed (KeyEvent e) {
		// TODO Auto-generated method stub
		if (mode != -1 && tetris != null) {
			switch (e.getKeyCode()) {
				case KeyEvent.VK_LEFT:
					tetris.shiftLeft(1);
					break;
				case KeyEvent.VK_RIGHT:
					tetris.shiftRight(1);
					break;
				case KeyEvent.VK_DOWN:
					tetris.shiftDown();
					break;
				case KeyEvent.VK_UP:
					tetris.rotateClockwise();
					break;
				case KeyEvent.VK_SPACE:
					tetris.drop();
					break;
				case KeyEvent.VK_P:
					tetris.changePausedEasy();
					mnuStart.setLabel(tetris.isPaused() ? "Resume" : "Pause");
					break;
				case KeyEvent.VK_S:
					if (thdTetris != null && thdTetris.isAlive()) {
						stopTetris();
						if (e.isControlDown()) {
							startTetris();
						}
					} else {
						startTetris();
					}
					break;
			}
			repaint();
		}
	}

	public void setPaused (boolean paused) {
		if (tetris.isStarted()) {
			tetris.setPaused(paused);
			mnuStart.setLabel(tetris.isPaused() ? "Resume" : "Pause");
		}
	}

	public boolean isStarted () {
		return tetris.isStarted();
	}

	public boolean isPaused () {
		return tetris.isPaused();
	}

	@Override
	public void keyReleased (KeyEvent e) {
		// TODO Auto-generated method stub
		switch (e.getKeyCode()) {
			case KeyEvent.VK_PRINTSCREEN:
				if (e.isAltDown() && robot != null) {
					setClipboardImage(robot.createScreenCapture(parent.getFakeBounds().getBounds()));
				}
				break;
		}
	}
}